/**********************************************************************

  Audacity: A Digital Audio Editor

  LoadEffects.cpp

  Dominic Mazzoni

**************************************************************************//**
\class BuiltinEffectsModule
\brief Internal module to auto register all built in effects.
*****************************************************************************/

#include "LoadEffects.h"

#include "Prefs.h"

#include "Effect.h"
#include "ModuleManager.h"
#include "PluginManager.h"

static bool sInitialized = false;

struct BuiltinEffectsModule::Entry {
    ComponentInterfaceSymbol name;
    BuiltinEffectsModule::Factory factory;
    bool excluded;

    using Entries = std::vector< Entry >;
    static Entries& Registry()
    {
        static Entries result;
        return result;
    }
};

void BuiltinEffectsModule::DoRegistration(
    const ComponentInterfaceSymbol& name, const Factory& factory, bool excluded)
{
    wxASSERT(!sInitialized);
    Entry::Registry().emplace_back(Entry { name, factory, excluded });
}

// ============================================================================
// Module registration entry point
//
// This is the symbol that Audacity looks for when the module is built as a
// dynamic library.
//
// When the module is builtin to Audacity, we use the same function, but it is
// declared static so as not to clash with other builtin modules.
// ============================================================================
DECLARE_PROVIDER_ENTRY(AudacityModule)
{
    // Create and register the importer
    // Trust the module manager not to leak this
    return std::make_unique<BuiltinEffectsModule>();
}

// ============================================================================
// Register this as a builtin module
// ============================================================================
DECLARE_BUILTIN_PROVIDER(BuiltinsEffectBuiltin);

///////////////////////////////////////////////////////////////////////////////
//
// BuiltinEffectsModule
//
///////////////////////////////////////////////////////////////////////////////

BuiltinEffectsModule::BuiltinEffectsModule()
{
}

BuiltinEffectsModule::~BuiltinEffectsModule()
{
}

// ============================================================================
// ComponentInterface implementation
// ============================================================================

PluginPath BuiltinEffectsModule::GetPath() const
{
    return {};
}

ComponentInterfaceSymbol BuiltinEffectsModule::GetSymbol() const
{
    return XO("Builtin Effects");
}

VendorSymbol BuiltinEffectsModule::GetVendor() const
{
    return XO("The Audacity Team");
}

wxString BuiltinEffectsModule::GetVersion() const
{
    // This "may" be different if this were to be maintained as a separate DLL
    return AUDACITY_VERSION_STRING;
}

TranslatableString BuiltinEffectsModule::GetDescription() const
{
    return XO("Provides builtin effects to Audacity");
}

// ============================================================================
// PluginProvider implementation
// ============================================================================

bool BuiltinEffectsModule::Initialize()
{
    for ( const auto& entry : Entry::Registry()) {
        auto path = wxString(BUILTIN_EFFECT_PREFIX) + entry.name.Internal();
        mEffects[ path ] = &entry;
    }
    sInitialized = true;
    return true;
}

void BuiltinEffectsModule::Terminate()
{
    // Nothing to do here
    return;
}

EffectFamilySymbol BuiltinEffectsModule::GetOptionalFamilySymbol()
{
    // Returns empty, because there should not be an option in Preferences to
    // disable the built-in effects.
    return {};
}

const FileExtensions& BuiltinEffectsModule::GetFileExtensions()
{
    static FileExtensions empty;
    return empty;
}

void BuiltinEffectsModule::AutoRegisterPlugins(PluginManagerInterface& pm)
{
    // Assume initial PluginManager::Save is not yet done

    // The set of built-in functions that are realtime capable may differ with
    // the plugin registry version
    bool rediscoverAll = !Regver_eq(pm.GetRegistryVersion(), REGVERCUR);

    TranslatableString ignoredErrMsg;
    for (const auto& pair : mEffects) {
        const auto& path = pair.first;
        if (rediscoverAll
            || !pm.IsPluginRegistered(path, &pair.second->name.Msgid())
            ) {
            DiscoverPluginsAtPath(path, ignoredErrMsg, [&](PluginProvider* provider, ComponentInterface* ident) -> const PluginID&
            {
                const auto& pluginId = PluginManagerInterface::DefaultRegistrationCallback(provider, ident);
                if (pair.second->excluded) {
                    PluginManager::Get().EnablePlugin(pluginId, false);
                }
                return pluginId;
            });
        }
    }
}

PluginPaths BuiltinEffectsModule::FindModulePaths(PluginManagerInterface&) const
{
    // Not really libraries
    PluginPaths names;
    for ( const auto& pair : mEffects ) {
        names.push_back(pair.first);
    }
    return names;
}

unsigned BuiltinEffectsModule::DiscoverPluginsAtPath(
    const PluginPath& path, TranslatableString& errMsg,
    const RegistrationCallback& callback)
{
    // At most one
    errMsg = {};
    auto effect = Instantiate(path);
    if (effect) {
        if (callback) {
            callback(this, effect.get());
        }
        return 1;
    }

    errMsg = XO("Unknown built-in effect name");
    return 0;
}

std::unique_ptr<ComponentInterface>
BuiltinEffectsModule::LoadPlugin(const PluginPath& path)
{
    // Acquires a resource for the application.
    return Instantiate(path);
}

bool BuiltinEffectsModule::CheckPluginExist(const PluginPath& path) const
{
    return mEffects.find(path) != mEffects.end();
}

// ============================================================================
// BuiltinEffectsModule implementation
// ============================================================================

std::unique_ptr<Effect> BuiltinEffectsModule::Instantiate(const PluginPath& path)
{
    wxASSERT(path.StartsWith(BUILTIN_EFFECT_PREFIX));
    auto iter = mEffects.find(path);
    if (iter != mEffects.end()) {
        return iter->second->factory();
    }

    wxASSERT(false);
    return nullptr;
}
